/*
 * SIT_Canvas.c : multi-purpose empty widget
 *
 * Written by T.Pierron, June 3, 2007.
 */

#include "SIT_P.h"
#include <stdio.h>

	TagList CanvasClass[] = {
		{ SIT_SuperClass,  NULL,          ___, SIT_INT,  (ULONG) WidgetClass },
		{ SIT_FrameStyle,  "frameStyle",  C__, SIT_INT,  OFFSET(SIT_Canvas, sc_Frame) },
		{ SIT_AutoFocus,   "autoFocus",   _SG, SIT_BOOL, OFFSET(SIT_Canvas, sc_AutoFocus) },
		{ SIT_UserBgClear, "userBgClear", C__, SIT_BOOL, OFFSET(SIT_Canvas, sc_UserBgClear) },
		{ SIT_CaretState,  "caretState",  _SG, SIT_INT,  OFFSET(SIT_Canvas, sc_CaretState) },
		{ SIT_CaretWidth,  "caretWidth",  _SG, SIT_INT,  OFFSET(SIT_Canvas, sc_CaretW) },
		{ SIT_CaretHeight, "caretHeight", _SG, SIT_INT,  OFFSET(SIT_Canvas, sc_CaretH) },
		{ SIT_CaretX,      "caretX",      _SG, SIT_INT,  OFFSET(SIT_Canvas, sc_CaretX) },
		{ SIT_CaretY,      "caretY",      _SG, SIT_INT,  OFFSET(SIT_Canvas, sc_CaretY) },
		{ SIT_TagEnd }
	};

// Caret management
static int SIT_AllocCaret(SIT_Widget w, APTR call_data, APTR user_data)
{
	SIT_Canvas c = (SIT_Canvas) w;

	if (! c->sc_CaretCreated)
	{
		CreateCaret(w->sw_Handle, NULL, c->sc_CaretW, c->sc_CaretH);
		SetCaretPos(c->sc_CaretX, c->sc_CaretY);
		if (c->sc_CaretState & SITV_CaretVisible) ShowCaret(w->sw_Handle);
		c->sc_CaretCreated = True;
	}
	return 0;
}

static int SIT_FreeCaret(SIT_Widget w, APTR call_data, APTR user_data)
{
	SIT_Canvas c = (SIT_Canvas) w;

	if (c->sc_CaretCreated)
	{
		if (c->sc_CaretState & SITV_CaretVisible) HideCaret(w->sw_Handle);
		DestroyCaret();
		c->sc_CaretCreated = False;
	}
	return 0;
}

/* Group caret modifications */
static int SIT_SetCanvasStyles(SIT_Widget w, APTR call_data, APTR user_data)
{
	SIT_Canvas c = (SIT_Canvas) w;

	if (c->sc_CaretCreated)
	{
		if (w->sw_Flags & SITF_Style1Changed)
		{
			/* Caret size change */
			if (c->sc_CaretState & SITV_CaretVisible) HideCaret(w->sw_Handle);
			DestroyCaret();
			CreateCaret(w->sw_Handle, NULL, c->sc_CaretW, c->sc_CaretH);
			SetCaretPos(c->sc_CaretX, c->sc_CaretY);
			if (c->sc_CaretState & SITV_CaretVisible) ShowCaret(w->sw_Handle);
			w->sw_Flags &= ~SITF_StylesChanged;
		}

		if (w->sw_Flags & SITF_Style2Changed)
		{
			if (c->sc_CaretState & SITV_CaretVisible)
			{
				HideCaret(w->sw_Handle);
				SetCaretPos(c->sc_CaretX, c->sc_CaretY);
				ShowCaret(w->sw_Handle);
			}
			else SetCaretPos(c->sc_CaretX, c->sc_CaretY);
		}
	}
	w->sw_Flags &= ~SITF_StylesChanged; /* Will be set when we get focus */
	w->sw_PostProcess = NULL;
	return 0;
}

static int SIT_SetCanvasValues(SIT_Widget w, APTR call_data, APTR user_data)
{
	TagList *     tag    = call_data;
	SIT_Variant * value  = user_data;
	SIT_Canvas    canvas = (SIT_Canvas) w;
	int           isSet  = 0;

	switch (tag->tl_TagID) {
	case SIT_CaretState:
		isSet = canvas->sc_CaretState ^ value->integer;

		if (isSet & SITV_HasCaret)
		{
			if (value->integer & SITV_HasCaret)
			{
				SIT_AddCallback(w, SITE_OnFocus, SIT_AllocCaret, NULL);
				SIT_AddCallback(w, SITE_OnBlur,  SIT_FreeCaret,  NULL);
			}
			else
			{
				SIT_DelCallback(w, SITE_OnFocus, SIT_AllocCaret, NULL);
				SIT_DelCallback(w, SITE_OnBlur,  SIT_FreeCaret,  NULL);
			}
		}
		if ((isSet & SITV_CaretVisible) && canvas->sc_CaretCreated)
		{
			if (value->integer & SITV_CaretVisible) ShowCaret(w->sw_Handle);
			else HideCaret(w->sw_Handle);
		}
		isSet = 0;
		canvas->sc_CaretState = value->integer;
		break;
	case SIT_UserBgClear:
		// XXX need to modify window class flags too
		canvas->sc_UserBgClear = value->boolean;
		if (value->boolean) w->sw_Flags |= SITF_HandleEraseBg;
		else                w->sw_Flags &= ~SITF_HandleEraseBg;
		break;
	case SIT_CaretWidth:
		if (canvas->sc_CaretW == value->integer) return 0;
		canvas->sc_CaretW = value->integer; isSet = SITF_Style1Changed;
		break;
	case SIT_CaretHeight:
		if (canvas->sc_CaretH == value->integer) return 0;
		canvas->sc_CaretH = value->integer; isSet = SITF_Style1Changed;
		break;
	case SIT_CaretX:      canvas->sc_CaretX = value->integer; isSet = SITF_Style2Changed; break;
	case SIT_CaretY:      canvas->sc_CaretY = value->integer; isSet = SITF_Style2Changed; break;
	default: return SIT_SetWidgetValue(w, call_data, user_data); // super class
	}
	if (isSet)
	{
		w->sw_Flags |= isSet;
		w->sw_PostProcess = SIT_SetCanvasStyles;
	}
	return 0;
}

static int SIT_MeasureCanvas(SIT_Widget w, APTR call_data, APTR user_data)
{
	SIZE * ret = call_data;

	if (w->sw_Children.lh_Count)
		SIT_LayoutWidgets(w, FitUsingOptimalBox);

	ret->cx = w->sw_Box.width  - w->sw_Box.x + w->sw_Margins[0] + w->sw_Margins[2];
	ret->cy = w->sw_Box.height - w->sw_Box.y + w->sw_Margins[1] + w->sw_Margins[3];
	w->sw_ChildBox = *ret;
	memset(&w->sw_Box, 0, sizeof w->sw_Box);
	return 1;
}

#define	DUMMYVAL   -32000

int SIT_CalcBorderSize(SIT_Widget w, APTR call_data, APTR user_data)
{
	RECT * r = call_data;
	RECT   m = {};
	switch (* (int *) user_data) {
	case SITV_EtchedHoriz: m.top  = m.bottom = 2; break;
	case SITV_EtchedVert:  m.left = m.right  = 2; break;
	case SITV_SunkenThick:
	case SITV_RaisedThick:
	case SITV_Etched:      m.top = m.bottom = m.left = m.right = 2; break;
	case SITV_Raised:
	case SITV_Sunken:      m.top = m.bottom = m.left = m.right = 1; break;
	default: return 0;
	}
	int * s, i;
	for (i = 0, s = w->sw_Margins; i < 4; i ++, s ++)
	{
		if (*s != DUMMYVAL) (&m.left)[i] = *s;
	}
	r->left   += m.left;
	r->top    += m.top;
	r->right  -= m.right;
	r->bottom -= m.bottom;
	return 1;
}

static int SIT_PaintBorder(SIT_Widget w, APTR hdc, APTR user_data)
{
	RECT r = {}, e;
	int  v, h, f, s;

	r.right  = w->sw_Box.width  - w->sw_Box.x;
	r.bottom = w->sw_Box.height - w->sw_Box.y;
	e = r;

	v = h = 0;
	f = EDGE_ETCHED;
	switch (* (int *) user_data) {
	case SITV_EtchedHoriz: s = BF_BOTTOM | BF_TOP; v = -2; break;
	case SITV_EtchedVert:  s = BF_LEFT | BF_RIGHT; h = -2; break;
	case SITV_Etched:      s = BF_RECT; v = h = -2; break;
	case SITV_Raised:      f = BDR_RAISEDOUTER; s = BF_RECT; v = h = -1; break;
	case SITV_Sunken:      f = BDR_SUNKENOUTER; s = BF_RECT; v = h = -1; break;
	default: return 0;
	}
	InflateRect(&r, h, v);
	if (w->sw_Margins[0] < r.left) r.left   = 0, s &= ~BF_LEFT;
	if (w->sw_Margins[1] < r.top)  r.top    = 0, s &= ~BF_TOP;
	if (w->sw_Margins[2] < -h)     r.right  = 0, s &= ~BF_RIGHT;
	if (w->sw_Margins[3] < -v)     r.bottom = 0, s &= ~BF_BOTTOM;
	DrawEdge(hdc, &e, f, s);

	#define canvas ((SIT_Canvas)w)
	if (canvas->sc_AutoFocus && canvas->sc_HasFocus)
		DrawFocusRect(hdc, &e);
	#undef canvas
	// XXX - sw_Parent doesn't always have a BgBrush
	FillRect(hdc, &r, w->sw_Parent->sw_BgBrush);
	return 1;
}

static inline void EmToPx(int * dim, int cy)
{
	if (*dim & (1<<31))
		*dim = cy * (*dim & 0xffff) / 25600 + ((*dim >> 16) & 0x7fff);
}


BOOL SIT_CreateCanvas(SIT_Widget w, va_list args)
{
	SIT_Canvas canvas = (APTR) w;
	int        style  = WS_CHILD;
	int        clsId  = 0;
	int        ext    = 0, i;
	RECT       border = {};
	WCHAR      clsName[48];

	w->sw_SetValue = SIT_SetCanvasValues;
	w->sw_OptimalWidth = SIT_MeasureCanvas;
	w->sw_Flags |= SITF_NCMargins;

	/* Default margins */
	for (i = 0; i < 4; w->sw_Margins[i] = DUMMYVAL, i ++);

	SIT_ParseTags(w, args, w->sw_Class = CanvasClass);

	if (IsVisible(w))            style |= WS_VISIBLE;
	if (HAS_TAB(w))              style |= WS_TABSTOP|WS_GROUP;
	if (!canvas->sc_UserBgClear) clsId |= CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS;

	switch (canvas->sc_Frame) {
	case SITV_NoFrame: break;
	case SITV_RaisedThick: style |= WS_THICKFRAME; break;
	case SITV_SunkenThick: ext |= WS_EX_CLIENTEDGE; break;
	/* Other values will be handled through WM_NCCALCSIZE and WM_NCPAINT */
	default:
		SIT_AddCallback(w, SITE_OnNCPaint, SIT_PaintBorder, &canvas->sc_Frame);
		SIT_AddCallback(w, SITE_OnNCCalcSize, SIT_CalcBorderSize, &canvas->sc_Frame);
	}
	SIT_CalcBorderSize(w, &border, &canvas->sc_Frame);

	/* Hack: only place where SIT_Handle can be set so that we can use custom controls */
	if (w->sw_Handle == NULL)
	{
		SIT_RegisterClass(w, clsName, clsId, IDC_ARROW, NULL);
		/* Create Win32 control, pos and dim will be set later */
		w->sw_Handle = CreateWindowEx(ext, clsName, w->sw_Name, style | WS_CLIPCHILDREN,
			0, 0, 0, 0, SIT_GetContainer(w), (HMENU) MAKE_ID(w), instance, NULL);

		/* If we handle WM_NCPAINT, we will have to handle WM_ERASEBKGND as well */
		if (HAS_EVT(w, SITE_OnNCPaint) && ! (w->sw_Flags & SITF_HandleEraseBg)) {
			canvas->sc_FullErase = True;
			w->sw_Flags |= SITF_HandleEraseBg;
		}

		if (w->sw_Handle == NULL) return FALSE;
	}
	SIT_SetDefaultFont(w);

	/* Width/height relative to font height: only allowed at creation time */
	if ((w->sw_Fixed.width  & (1<<31)) ||
	    (w->sw_Fixed.height & (1<<31)))
	{
		HDC     hdc = GetDC(w->sw_Handle);
		HGDIOBJ old = SelectObject(hdc, w->sw_Font);
		SIZE    sz;

		GetTextExtentPoint32(hdc, L"M", 1, &sz);
		EmToPx(&w->sw_Fixed.width,  sz.cy);
		EmToPx(&w->sw_Fixed.height, sz.cy);
		SelectObject(hdc, old);
		ReleaseDC(w->sw_Handle, hdc);
	}
	int * m;
	for (i = 0, m = w->sw_Margins; i < 4; i ++, m ++) {
		int b = (&border.left)[i];
		if (i >= 2) b = -b;
		if (*m == DUMMYVAL || *m < b) *m = b;
	}

	if (w->sw_Flags & SITF_FixedWidth)  w->sw_Fixed.width  -= border.right - border.left;
	if (w->sw_Flags & SITF_FixedHeight) w->sw_Fixed.height -= border.bottom - border.top;

	if (w->sw_PostProcess)
		w->sw_PostProcess(w, NULL, NULL);

	SetProp(w->sw_Handle, sit_class, (HANDLE) w);

	return TRUE;
}
